package com.enzo.prms.common.exception.context;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.util.Assert;

import com.enzo.prms.common.exception.annotation.Exceptional;
import com.enzo.prms.common.exception.base.BaseAppRuntimeException;
import com.enzo.prms.common.exception.base.SystemException;
import com.enzo.prms.common.exception.base.handler.DefaultHandler;
import com.enzo.prms.common.exception.base.handler.ExceptionHandler;
import com.enzo.prms.common.exception.config.ExceptionDefinition;
import com.enzo.prms.common.exception.util.ClassUtil;

public class ExceptionContext {
	private volatile static ExceptionContext instance=null;
	/**异常定义集合*/
	private Map<Class<?>, ExceptionDefinition> definitionMap=new HashMap<Class<?>, ExceptionDefinition>();
	/**异常处理器集合*/
	private Map<String, ExceptionHandler> handlerMap = new HashMap<String, ExceptionHandler>();
	private static final String EXCEPTION_PACKAGE="com.enzo.prms.common.exception.base";

	public static ExceptionContext getInstance(){
		if(instance==null){
			synchronized (ExceptionContext.class) {
				if(instance==null){
					instance=new ExceptionContext();
				}
			}
		}
		return instance;
	}
	
	private ExceptionContext() {
		init();
	}
	
	/**
	 * 初始化，将异常定义初始化到Context中
	 */
	private void init(){
		try{
			Set<Class<?>> exceptionClazzes=ClassUtil.getClassesFromPackage(EXCEPTION_PACKAGE);
			for (Class<?> c : exceptionClazzes) {
				//判断class是否存在Exceptional.class注释 
				boolean flag=c.isAnnotationPresent(Exceptional.class);
				if(flag){
					Exceptional el=(Exceptional)c.getAnnotation(Exceptional.class);
					String errorCode=el.errorCode();
					Class<? extends ExceptionHandler>[] handlers=el.handlers();
					ExceptionDefinition definition=new ExceptionDefinition();
					definition.setErrorCode(errorCode);
					definitionMap.put(c, definition);
					addExceptionHandlers(c, handlers);
				}
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	

	public void addExceptionHandler(Class<?> expClazz,
			Class<? extends ExceptionHandler> handlerClazz) {
		try {
			ExceptionDefinition definition=definitionMap.get(expClazz);
			if(definition == null){
				throw new IllegalArgumentException(expClazz.getName() + "not in the context, please configure or add it to the context first!");
			}
			ExceptionHandler handler = handlerMap.get(handlerClazz.getName());
			if (handler == null) {
				handler = handlerClazz.newInstance();
				handlerMap.put(handlerClazz.getName(), handler);
			}
			definition.getHandlerNames().add(handlerClazz.getName());
		} catch (Exception e) {
			throw new SystemException("Add exception handler to context failure!", e);
		}
	}
	
	public void addExceptionHandlers(Class<?> expClazz,Class<? extends ExceptionHandler>... handlerClazzes){
		for(Class<? extends ExceptionHandler> handlerClazz:handlerClazzes){
			addExceptionHandler(expClazz, handlerClazz);
		}
	}
	
	public void removeExceptionHandler(Class<?> expClazz,Class<? extends ExceptionHandler> handlerClazz){
		Assert.isTrue(containsException(expClazz));
		String handlerName = handlerClazz.getName();
        getExceptionDefinition(expClazz).getHandlerNames().remove(handlerName);
        Collection<ExceptionDefinition> definitons = definitionMap.values();
        boolean isClearHandler = true;
        for (ExceptionDefinition expDefinition : definitons) {
            if (expDefinition.getHandlerNames().contains(handlerName)) {
                isClearHandler = false;
                break;
            }
        }

        if (isClearHandler) {
        	//TODO 
            handlerMap.remove(handlerMap.get(handlerName));
        }
	}
	
	public List<ExceptionHandler> getExceptionHandlers(Class<?> expClazz){
        ExceptionDefinition definition = getExceptionDefinition(expClazz);
        if (null != definition) {
            Set<String> handlerNames = definition.getHandlerNames();
            List<ExceptionHandler> handlerList = new ArrayList<ExceptionHandler>(handlerNames.size());
            for (String handlerName : handlerNames) {
                ExceptionHandler handler = handlerMap.get(handlerName);
                handlerList.add(handler);
            }
            return handlerList;
        } else {
            return Collections.<ExceptionHandler> emptyList();
        }
    }
	
	public ExceptionDefinition getExceptionDefinition(Class<?> expClazz) {
        if (containsException(expClazz)) {
            return definitionMap.get(expClazz);  
        } else if (BaseAppRuntimeException.class.isAssignableFrom(expClazz.getSuperclass())) {
            return getExceptionDefinition(expClazz.getSuperclass());
        } else {
            return null;
        }
    }
	
	public String getErrorCode(Class<?> expClazz){
        ExceptionDefinition definition = getExceptionDefinition(expClazz);
        if (null != definition) {
            return definition.getErrorCode();
        } else {
            return "";
        }
    }
	
	public boolean containsException(Class<?> expClazz) {
        return (definitionMap.containsKey(expClazz));
    }
	
	public ExceptionHandler getExceptionHandler(Class<? extends ExceptionHandler> handlerClazz){
		try{
			if(handlerMap.containsKey(handlerClazz.getName())){
				return handlerMap.get(handlerClazz.getName());
			}else{
				ExceptionHandler handler=handlerClazz.newInstance();
				handlerMap.put(DefaultHandler.class.getName(), handler);
				return handler;
			}
		}catch (Exception e) {
			throw new SystemException("GetExceptionHandler Error!",e);
		}
	}
	
	

}
